package modbus

import (
	"test/protocol"
	"test/util"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

func init() {
	protocol.RegisterProtocolFactory("modbustcp", &ModbusTcpFactory{})
}

type ModbusTcpFactory struct {
	Name string
}

func (mf ModbusTcpFactory) Create() protocol.Protocol {
	mf.Name = "modbustcp"
	m := &ModbusTcp{}
	return m
}
func (mf ModbusTcpFactory) InitPara([]interface{}) {

}

type ModbusInterface interface {
	reqCoilStatus1(uint16, uint16)
	reqInputStatus2(uint16, uint16)
	reqHoldingReg3(uint16, uint16)
	reqInputReg4(uint16, uint16)
	reqWriteSingleCoil5(uint16, bool)
	reqWriteMultiCoils15(uint16, ...bool)
	reqWriteSingleReg6(uint16, uint16)
	reqWriteMultiRegs16(uint16, ...uint16)
	rspAbnormal(uint8, uint8) error
	rspCoilStatus([]byte)
	rspInputStatus([]byte)
	rspHoldingReg([]byte)
	rspInputReg([]byte)
}
type ModbusTcp struct {
	protocol.ProtocolInstance
	rtu                  int16
	transactionElementID uint16
	protocolID           uint16
	length               uint16
	unitID               uint8
	funcCode             uint8
	regAddress           uint16 //请求命令时每帧报文的寄存器地址
	reqRegAddress        uint16 //请求寄存器地址
	baseRegAddress       uint16 //寄存器偏移基址
	writeSuccess         bool
	readSuccess          bool
	byteMode             int
	lastsendtime         int64
	anolognum            uint16
	digitalnum           uint16
	recvRegNum           int
}

func (modbus *ModbusTcp) Init(conn *net.TCPConn, rtu int16) {
	modbus.SetConn(conn)
	modbus.rtu = rtu
	modbus.writeSuccess = false
	modbus.readSuccess = false
	modbus.baseRegAddress = 100
	//modbus.reqCoilStatus1(100, 104)
	//modbus.reqInputStatus2(100, 32)
	//modbus.reqHoldingReg3(100, 32)
	//modbus.reqInputReg4(255, 30)
	//modbus.reqWriteMultiCoils15(20, true, true, true, true, false)
	//modbus.reqWriteSingleReg6(20, 255)
	//modbus.reqWriteMultiRegs16(30, 255, 256, 257, 258, 259, 260)
}
func (modbus *ModbusTcp) Handler(message string) {
	data := []byte(message)
	defer func() {
		if e := recover(); e != nil {
			log.Printf("Panic: %s\r\n", e)
		}
		_ = data
	}()
	modbus.readSuccess = false
	reqLen := strings.Count(message, "") - 1
	var offset int
	var index int
	for {
		index += offset
		if index >= reqLen {
			break
		}
		if (reqLen - index) < 8 {
			log.Println("error message return")
			break
		}
		transactionElementID := data[0+index : 2+index]
		protocolID := data[2+index : 4+index]
		length := data[4+index : 6+index]
		modbus.transactionElementID = uint16(util.BytesToInt16(transactionElementID, false))
		modbus.protocolID = uint16(util.BytesToInt16(protocolID, false))
		modbus.length = uint16(util.BytesToInt16(length, false))
		if modbus.length > uint16(reqLen) {
			offset = 1
			continue
		}
		modbus.unitID = uint8(data[6+index])

		modbus.funcCode = uint8(data[7+index])
		if modbus.funcCode > 0x80 {
			err := modbus.rspAbnormal(modbus.funcCode, data[8+index])
			if err != nil {
				log.Println(err)
			}
			if modbus.funcCode < ForceWriteSingleCoil {
				modbus.readSuccess = false
			} else {
				modbus.writeSuccess = false
			}
		} else {
			dataSlice := data[8+index : modbus.length+6+uint16(index)]
			funcCode := modbus.funcCode
			switch funcCode {
			case CoilStatusFunctionCode1:
				modbus.rspCoilStatus(dataSlice)
				break
			case InputStatusFunctionCode2:
				modbus.rspInputStatus(dataSlice)
				break
			case HoldingRegFunctionCode3:
				modbus.rspHoldingReg(dataSlice)
				break
			case InputRegFunctionCode4:
				modbus.rspInputReg(dataSlice)
				break
			case ForceWriteSingleCoil:
				modbus.writeSuccess = true
				log.Println("Write Single Coil")
				break
			case ForceWriteMultiCoils:
				modbus.writeSuccess = true
				log.Println("Write Multi Coils")
				break
			case WriteSingleRegFunctionCode6:
				modbus.writeSuccess = true
				log.Println("Write Single Reg ")
				break
			case WriteMultiRegsFunctionCode16:
				modbus.writeSuccess = true
				log.Println("Write Multi Regs")
				break
			}
		}
		offset = int(modbus.length) + 6
	}
}

func (modbus *ModbusTcp) Run() int {
	base := modbus.baseRegAddress
	if (time.Now().Unix() - modbus.lastsendtime) > 10 {
		gin := modbus.anolognum / 32
		inf := modbus.anolognum % 32
		modbus.recvRegNum = 0
		for index := 0; index < int(gin); index++ {
			modbus.reqHoldingReg3(base+uint16(index)*32, 32)
			time.Sleep(10 * time.Microsecond)
		}
		modbus.reqHoldingReg3(base+gin*32, inf)
	}
	return 0
}
func (modbus *ModbusTcp) Delete() {
}
func (modbus *ModbusTcp) reqCoilStatus1(regaddr uint16, regQuantity uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             CoilStatusFunctionCode1,
		regAddress:           regaddr - 1,
		regQuantity:          regQuantity,
	}
	modbus.reqRegAddress = regaddr
	apdu.length = 6
	data, alen := apdu.enpack(true)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	modbus.lastsendtime = time.Now().Unix()
}
func (modbus *ModbusTcp) reqInputStatus2(regaddr uint16, regQuantity uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             InputStatusFunctionCode2,
		regAddress:           regaddr - 1,
		regQuantity:          regQuantity,
	}
	modbus.reqRegAddress = regaddr
	data, alen := apdu.enpack(true)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	modbus.lastsendtime = time.Now().Unix()
}
func (modbus *ModbusTcp) reqHoldingReg3(regaddr uint16, regQuantity uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             HoldingRegFunctionCode3,
		regAddress:           regaddr - 1,
		regQuantity:          regQuantity,
	}
	modbus.reqRegAddress = regaddr
	data, alen := apdu.enpack(true)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	modbus.lastsendtime = time.Now().Unix()
}
func (modbus *ModbusTcp) reqInputReg4(regaddr uint16, regQuantity uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             InputRegFunctionCode4,
		regAddress:           regaddr - 1,
		regQuantity:          regQuantity,
	}
	modbus.reqRegAddress = regaddr
	data, alen := apdu.enpack(true)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	modbus.lastsendtime = time.Now().Unix()
}

func (modbus *ModbusTcp) reqWriteSingleCoil5(regaddr uint16, value bool) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             ForceWriteSingleCoil,
		regAddress:           regaddr - 1,
	}
	var val [1]byte
	if value {
		val[0] = 1
	} else {
		val[0] = 0
	}
	apdu.body = string(val[:])
	data, alen := apdu.enpack(false)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
}
func (modbus *ModbusTcp) reqWriteMultiCoils15(regaddr uint16, values ...bool) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             ForceWriteMultiCoils,
		regAddress:           regaddr - 1,
	}

	length := len(values)
	var val = make([]byte, length)
	for index, value := range values {
		if value {
			val[index] = 1
		} else {
			val[index] = 0
		}
	}
	apdu.body = string(val[:])
	data, alen := apdu.enpack(false)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	val = nil
}
func (modbus *ModbusTcp) reqWriteSingleReg6(regaddr uint16, value uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             WriteSingleRegFunctionCode6,
		regAddress:           regaddr - 1,
	}
	val := util.Int16ToByte(int16(value), false)
	apdu.body = string(val[:])
	data, alen := apdu.enpack(false)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
}
func (modbus *ModbusTcp) reqWriteMultiRegs16(regaddr uint16, values ...uint16) {
	var apdu = &ADU{
		transactionElementID: 0,
		protocolID:           0,
		unitID:               uint8(modbus.rtu),
		funcCode:             WriteMultiRegsFunctionCode16,
		regAddress:           regaddr - 1,
	}
	length := len(values)
	var val = make([]byte, length*2)
	offset := 0
	for _, value := range values {
		v := util.Int16ToByte(int16(value), false)
		val[offset] = v[0]
		val[offset+1] = v[1]
		offset += 2
	}
	apdu.body = string(val[:])
	data, alen := apdu.enpack(false)
	if alen > 0 {
		modbus.Send(string(data[0:alen]))
		util.Printpacket(string(data[:]))
	}
	val = nil
}
func (modbus *ModbusTcp) rspHandlerbyByte(data []byte) {
	for index, value := range data[:] {
		tmpvalue := uint8(value)
		for offset := 0; offset < 8; offset++ {
			fmt.Println(uint16(index*8+offset)+modbus.reqRegAddress, tmpvalue&0x01)
			tmpvalue >>= 1
		}
	}
}
func (modbus *ModbusTcp) rspHandlerby2Bytes(bytesNum int, data []byte) {
	offset := 0
	for index := 0; index < bytesNum; index += 2 {
		value := util.BytesToInt16(data[index:index+2], false)
		fmt.Println(value)
		offset++
	}
	modbus.recvRegNum += offset
}
func (modbus *ModbusTcp) rspCoilStatus(data []byte) {
	log.Println("RspCoilStatus", modbus.reqRegAddress)
	modbus.readSuccess = true
	util.Printpacket(string(data[:]))
	modbus.rspHandlerbyByte(data[1:])
}
func (modbus *ModbusTcp) rspInputStatus(data []byte) {
	log.Println("RspInputStatus")
	modbus.readSuccess = true
	util.Printpacket(string(data[:]))
	modbus.rspHandlerbyByte(data[1:])
}
func (modbus *ModbusTcp) rspHoldingReg(data []byte) {
	log.Println("RspHoldingReg")
	modbus.readSuccess = true
	util.Printpacket(string(data[:]))
	modbus.rspHandlerby2Bytes(int(data[0]), data[1:])
}
func (modbus *ModbusTcp) rspInputReg(data []byte) {
	log.Println("RspInputReg")
	modbus.readSuccess = true
	util.Printpacket(string(data[:]))
	modbus.rspHandlerby2Bytes(int(data[0]), data[1:])
}
func (modbus *ModbusTcp) rspAbnormal(funcCode uint8, reason uint8) (err error) {
	realfuncCode := funcCode - 0x80
	switch reason {
	case InvalidFunctionCode:
		err = fmt.Errorf("FunctionCode:%d Invalid FunctionCode", realfuncCode)
		break
	case InvalidDataAddress:
		err = fmt.Errorf("FunctionCode:%d Invalid Data Address", realfuncCode)
		break
	case InvalidDataValue:
		err = fmt.Errorf("FunctionCode:%d Invalid Data Value", realfuncCode)
		break
	case ServerFailure:
		err = fmt.Errorf("FunctionCode:%d Server Failure", realfuncCode)
		break
	case ServerBusy:
		err = fmt.Errorf("FunctionCode:%d Server Busy", realfuncCode)
		break
	}
	return
}
